/*-------------------------------------------------------------------------
 *
 * nodeMaterial.c
 *	  处理材质化节点的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeMaterial.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *		ExecMaterial			- 材料化子计划的结果
 *		ExecInitMaterial		- 初始化节点和子节点
 *		ExecEndMaterial			- 关闭节点和子节点
 *
 */
#include "postgres.h"

#include "executor/executor.h"
#include "executor/nodeMaterial.h"
#include "miscadmin.h"

/* ----------------------------------------------------------------
 *		ExecMaterial
 *
 *		只要我们达到元组存储中收集的数据末尾，
 *		每次调用从子计划中收集一行新数据，并将其暂存
 *		在元组存储中，再返回。只有在我们被要求向后扫描、
 *		重新扫描或标记/恢复时，才会读取元组存储。
 *
 * ----------------------------------------------------------------
 */
static TupleTableSlot *			/* 来自子计划的结果元组 */
fc_ExecMaterial(PlanState *fc_pstate)
{
	MaterialState *fc_node = castNode(MaterialState, fc_pstate);
	EState	   *fc_estate;
	ScanDirection fc_dir;
	bool		fc_forward;
	Tuplestorestate *fc_tuplestorestate;
	bool		fc_eof_tuplestore;
	TupleTableSlot *fc_slot;

	CHECK_FOR_INTERRUPTS();

	/*
	 * 从节点获取状态信息
	 */
	fc_estate = fc_node->ss.ps.state;
	fc_dir = fc_estate->es_direction;
	fc_forward = ScanDirectionIsForward(fc_dir);
	fc_tuplestorestate = fc_node->tuplestorestate;

	/*
	 * 如果是第一次执行，并且我们需要一个元组存储，进行初始化。
	 */
	if (fc_tuplestorestate == NULL && fc_node->eflags != 0)
	{
		fc_tuplestorestate = tuplestore_begin_heap(true, false, work_mem);
		tuplestore_set_eflags(fc_tuplestorestate, fc_node->eflags);
		if (fc_node->eflags & EXEC_FLAG_MARK)
		{
			/*
			 * 分配第二个读取指针作为标记。我们知道它
			 * 必须具有索引 1，因此不需要存储它。
			 */
			int			fc_ptrno PG_USED_FOR_ASSERTS_ONLY;

			fc_ptrno = tuplestore_alloc_read_pointer(fc_tuplestorestate,
												  fc_node->eflags);
			Assert(fc_ptrno == 1);
		}
		fc_node->tuplestorestate = fc_tuplestorestate;
	}

	/*
	 * 如果我们没有到达元组存储的末尾，或者正在向后移动，尝试
	 * 从元组存储中获取元组。
	 */
	fc_eof_tuplestore = (fc_tuplestorestate == NULL) ||
		tuplestore_ateof(fc_tuplestorestate);

	if (!fc_forward && fc_eof_tuplestore)
	{
		if (!fc_node->eof_underlying)
		{
			/*
			 * 在元组存储 EOF 处反向时，第一个
			 * gettupleslot 调用将获取最后添加的元组；但我们想
			 * 返回在此之前的那个（如果可能的话）。所以要进行额外
			 * 的获取。
			 */
			if (!tuplestore_advance(fc_tuplestorestate, fc_forward))
				return NULL;	/* 元组存储必须为空 */
		}
		fc_eof_tuplestore = false;
	}

	/*
	 * 如果我们可以从元组存储中获取另一个元组，则返回它。
	 */
	fc_slot = fc_node->ss.ps.ps_ResultTupleSlot;
	if (!fc_eof_tuplestore)
	{
		if (tuplestore_gettupleslot(fc_tuplestorestate, fc_forward, false, fc_slot))
			return fc_slot;
		if (fc_forward)
			fc_eof_tuplestore = true;
	}

	/*
	 * 如果有必要，尝试从子计划中获取另一行。
	 *
	 * 注意：eof_underlying 状态变量存在是为了缩短
	 * 进一步的子计划调用。不幸的是，这是必需的，因为某些计划
	 * 节点类型在已经返回 NULL 时再被调用时并不稳健。
	 */
	if (fc_eof_tuplestore && !fc_node->eof_underlying)
	{
		PlanState  *fc_outerNode;
		TupleTableSlot *fc_outerslot;

		/*
		 * 我们只能在 forward==true 时到达这里，所以不必担心
		 * 子计划将向哪个方向移动。
		 */
		fc_outerNode = outerPlanState(fc_node);
		fc_outerslot = ExecProcNode(fc_outerNode);
		if (TupIsNull(fc_outerslot))
		{
			fc_node->eof_underlying = true;
			return NULL;
		}

		/*
		 * 将返回的元组的副本附加到元组存储中。注意：由于
		 * 元组存储肯定处于 EOF 状态，因此其读取位置将会
		 * 向前移动到添加的元组。这正是我们想要的。
		 */
		if (fc_tuplestorestate)
			tuplestore_puttupleslot(fc_tuplestorestate, fc_outerslot);

		ExecCopySlot(fc_slot, fc_outerslot);
		return fc_slot;
	}

	/*
	 * 没有什么剩下……
	 */
	return ExecClearTuple(fc_slot);
}

/* ----------------------------------------------------------------
 *		ExecInitMaterial
 * ----------------------------------------------------------------
 */
MaterialState *
ExecInitMaterial(Material *fc_node, EState *fc_estate, int fc_eflags)
{
	MaterialState *fc_matstate;
	Plan	   *fc_outerPlan;

	/*
	 * 创建状态结构
	 */
	fc_matstate = makeNode(MaterialState);
	fc_matstate->ss.ps.plan = (Plan *) fc_node;
	fc_matstate->ss.ps.state = fc_estate;
	fc_matstate->ss.ps.ExecProcNode = fc_ExecMaterial;

	/*
	 * 我们必须有一个元组存储来缓存子计划的输出，以便进行向后
	 * 扫描或标记/恢复。我们还希望在可能多次需要回放时
	 * 对子计划输出进行材料化。然而，如果这些情况都不适用，
	 * 我们可以跳过存储数据。
	 */
	fc_matstate->eflags = (fc_eflags & (EXEC_FLAG_REWIND |
								  EXEC_FLAG_BACKWARD |
								  EXEC_FLAG_MARK));

	/*
	 * 元组存储对标志位的解释与一般执行器的含义稍有不同：
	 * 它并不认为 BACKWARD 必然意味着“向后一直到起点”。
	 * 如果要求支持 BACKWARD，我们必须在元组存储的 eflags 中
	 * 包括 REWIND，否则 tuplestore_trim 可能会丢掉太多。
	 */
	if (fc_eflags & EXEC_FLAG_BACKWARD)
		fc_matstate->eflags |= EXEC_FLAG_REWIND;

	fc_matstate->eof_underlying = false;
	fc_matstate->tuplestorestate = NULL;

	/*
	 * 杂项初始化
	 *
	 * 材料化节点不需要 ExprContexts，因为它们从不调用
	 * ExecQual 或 ExecProject。
	 */

	/*
	 * 初始化子节点
	 *
	 * 我们将子节点与支持 REWIND、BACKWARD 或
	 * MARK/RESTORE 的需求隔离。
	 */
	fc_eflags &= ~(EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK);

	fc_outerPlan = outerPlan(fc_node);
	outerPlanState(fc_matstate) = ExecInitNode(fc_outerPlan, fc_estate, fc_eflags);

	/*
	 * 初始化结果类型和槽位。无需初始化投影信息
	 * 因为此节点不进行投影。
	 *
	 * material 节点仅返回其物化关系中的元组。
	 */
	ExecInitResultTupleSlotTL(&fc_matstate->ss.ps, &TTSOpsMinimalTuple);
	fc_matstate->ss.ps.ps_ProjInfo = NULL;

	/*
	 * 初始化元组类型。
	 */
	ExecCreateScanSlotFromOuterPlan(fc_estate, &fc_matstate->ss, &TTSOpsMinimalTuple);

	return fc_matstate;
}

/* ----------------------------------------------------------------
 *		ExecEndMaterial
 * ----------------------------------------------------------------
 */
void ExecEndMaterial(MaterialState *fc_node)
{
	/*
	 * 清空元组表
	 */
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	/*
	 * 释放元组存储资源
	 */
	if (fc_node->tuplestorestate != NULL)
		tuplestore_end(fc_node->tuplestorestate);
	fc_node->tuplestorestate = NULL;

	/*
	 * 关闭子计划
	 */
	ExecEndNode(outerPlanState(fc_node));
}

/* ----------------------------------------------------------------
 *		ExecMaterialMarkPos
 *
 *		调用元组存储以保存存储文件中的当前位置。
 * ----------------------------------------------------------------
 */
void ExecMaterialMarkPos(MaterialState *fc_node)
{
	Assert(fc_node->eflags & EXEC_FLAG_MARK);

	/*
	 * 如果我们还没有物化，只需返回。
	 */
	if (!fc_node->tuplestorestate)
		return;

	/*
	 * 将活动读取指针复制到标记中。
	 */
	tuplestore_copy_read_pointer(fc_node->tuplestorestate, 0, 1);

	/*
	 * 由于我们可能已经提前标记，尝试截断元组存储。
	 */
	tuplestore_trim(fc_node->tuplestorestate);
}

/* ----------------------------------------------------------------
 *		ExecMaterialRestrPos
 *
 *		调用元组存储以恢复最后保存的文件位置。
 * ----------------------------------------------------------------
 */
void ExecMaterialRestrPos(MaterialState *fc_node)
{
	Assert(fc_node->eflags & EXEC_FLAG_MARK);

	/*
	 * 如果我们还没有物化，只需返回。
	 */
	if (!fc_node->tuplestorestate)
		return;

	/*
	 * 将标记复制到活动读取指针。
	 */
	tuplestore_copy_read_pointer(fc_node->tuplestorestate, 1, 0);
}

/* ----------------------------------------------------------------
 *		ExecReScanMaterial
 *
 *		重新扫描物化关系。
 * ----------------------------------------------------------------
 */
void ExecReScanMaterial(MaterialState *fc_node)
{
	PlanState  *fc_outerPlan = outerPlanState(fc_node);

	ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);

	if (fc_node->eflags != 0)
	{
		/*
		 * 如果我们还没有物化，只需返回。如果外部计划的
		 * chgParam 不为 NULL，则将由 ExecProcNode 重新扫描，
		 * 否则无需重新扫描。
		 */
		if (!fc_node->tuplestorestate)
			return;

		/*
		 * 如果子节点需要重新扫描，则我们会忘记之前存储的
		 * 结果；我们必须重新读取子计划并重新存储。同样，如果我们
		 * 告诉元组存储它不需要支持重新扫描，我们就会失去并且必须
		 * 重新读取。（这种情况在常见情况下不应该发生；否则我们的
		 * 调用者通过不传递 EXEC_FLAG_REWIND 给我们而撒了谎。）
		 *
		 * 否则我们只需倒带并重新扫描存储的输出。子节点的状态不会改变。
		 */
		if (fc_outerPlan->chgParam != NULL ||
			(fc_node->eflags & EXEC_FLAG_REWIND) == 0)
		{
			tuplestore_end(fc_node->tuplestorestate);
			fc_node->tuplestorestate = NULL;
			if (fc_outerPlan->chgParam == NULL)
				ExecReScan(fc_outerPlan);
			fc_node->eof_underlying = false;
		}
		else
			tuplestore_rescan(fc_node->tuplestorestate);
	}
	else
	{
		/* 在这种情况下，我们只是传递子查询的输出 */

		/*
		 * 如果子节点的 chgParam 不为 null，则计划将由
		 * 首先 ExecProcNode 重新扫描。
		 */
		if (fc_outerPlan->chgParam == NULL)
			ExecReScan(fc_outerPlan);
		fc_node->eof_underlying = false;
	}
}
